#include <iostream>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <cstring>
#include "fv_txt_utls.h"
#include "Log.h"

// WGL specific extensions for v3.0+ //////////////////////////////////////////
#ifdef _WIN32
#include <windows.h>
typedef const char* (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc);
PFNWGLGETEXTENSIONSSTRINGARBPROC    pwglGetExtensionsStringARB = 0;
#define wglGetExtensionsStringARB  pwglGetExtensionsStringARB
#endif

#ifdef __APPLE__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif

// function pointers for VBO Extension
// Windows needs to get function pointers from ICD OpenGL drivers,
// because opengl32.dll does not support extensions higher than v1.1.
#ifdef _WIN32
extern PFNGLGENBUFFERSARBPROC            pglGenBuffersARB = 0;             // VBO Name Generation Procedure
extern PFNGLBINDBUFFERARBPROC            pglBindBufferARB = 0;             // VBO Bind Procedure
extern PFNGLBUFFERDATAARBPROC            pglBufferDataARB = 0;             // VBO Data Loading Procedure
extern PFNGLBUFFERSUBDATAARBPROC         pglBufferSubDataARB = 0;          // VBO Sub Data Loading Procedure
extern PFNGLDELETEBUFFERSARBPROC         pglDeleteBuffersARB = 0;          // VBO Deletion Procedure
extern PFNGLGETBUFFERPARAMETERIVARBPROC  pglGetBufferParameterivARB = 0;   // return various parameters of VBO
extern PFNGLMAPBUFFERARBPROC             pglMapBufferARB = 0;              // map VBO procedure
extern PFNGLUNMAPBUFFERARBPROC           pglUnmapBufferARB = 0;            // unmap VBO procedure
#endif

// version 2.0 or greater
#define GL_SHADING_LANGUAGE_VERSION       0x8B8C



///////////////////////////////////////////////////////////////////////////////
// extract openGL info
// This function must be called after GL rendering context opened.
///////////////////////////////////////////////////////////////////////////////
bool glInfo::getInfo(unsigned int param)
{
    const char* str = 0;
    char* tok = 0;

    // get vendor string
    str = (const char*)glGetString(GL_VENDOR);
    if(str) this->vendor = str;                  // check NULL return value
    else return false;

    // get renderer string
    str = (const char*)glGetString(GL_RENDERER);
    if(str) this->renderer = str;                // check NULL return value
    else return false;

    // get version string
    str = (const char*)glGetString(GL_VERSION);
    if(str) this->version = str;                 // check NULL return value
    else return false;

    // get version string (v2.0+)
    str = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
    if(str) this->glslVersion = str;            // check NULL return value
    else glslVersion = "";

    // get all extensions as a string
    str = (const char*)glGetString(GL_EXTENSIONS);

    // split extensions
    if(str)
    {
        tok = strtok((char*)str, " ");
        while(tok)
        {
            this->extensions.push_back(tok);    // put a extension into struct
            tok = strtok(0, " ");               // next token
        }
    }

    // get WGL specific extensions for v3.0+
#ifdef _WIN32 //===========================================
    wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
    if(wglGetExtensionsStringARB && param)
    {
        str = wglGetExtensionsStringARB((HDC)param);
        if(str)
        {
            tok = strtok((char*)str, " ");
            while(tok)
            {
                this->extensions.push_back(tok);    // put a extension into struct
                tok = strtok(0, " ");               // next token
            }
        }
    }
#endif //==================================================

    // sort extension by alphabetical order
    std::sort(this->extensions.begin(), this->extensions.end());

    // get number of color bits
    glGetIntegerv(GL_RED_BITS, &this->redBits);
    glGetIntegerv(GL_GREEN_BITS, &this->greenBits);
    glGetIntegerv(GL_BLUE_BITS, &this->blueBits);
    glGetIntegerv(GL_ALPHA_BITS, &this->alphaBits);

    // get depth bits
    glGetIntegerv(GL_DEPTH_BITS, &this->depthBits);

    // get stecil bits
    glGetIntegerv(GL_STENCIL_BITS, &this->stencilBits);

    // get max number of lights allowed
    glGetIntegerv(GL_MAX_LIGHTS, &this->maxLights);

    // get max texture resolution
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &this->maxTextureSize);

    // get max number of clipping planes
    glGetIntegerv(GL_MAX_CLIP_PLANES, &this->maxClipPlanes);

    // get max modelview and projection matrix stacks
    glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &this->maxModelViewStacks);
    glGetIntegerv(GL_MAX_PROJECTION_STACK_DEPTH, &this->maxProjectionStacks);
    glGetIntegerv(GL_MAX_ATTRIB_STACK_DEPTH, &this->maxAttribStacks);

    // get max texture stacks
    glGetIntegerv(GL_MAX_TEXTURE_STACK_DEPTH, &this->maxTextureStacks);

    return true;
}



///////////////////////////////////////////////////////////////////////////////
// check if the video card support a certain extension
///////////////////////////////////////////////////////////////////////////////
bool glInfo::isExtensionSupported(const std::string& ext)
{
    // search corresponding extension
    std::vector<std::string>::const_iterator iter = this->extensions.begin();
    std::vector<std::string>::const_iterator endIter = this->extensions.end();

    while(iter != endIter)
    {
        if(ext == *iter)
            return true;
        else
            ++iter;
    }
    return false;
}



///////////////////////////////////////////////////////////////////////////////
// print OpenGL info to screen and save to a file
///////////////////////////////////////////////////////////////////////////////
void glInfo::printSelf()
{
    std::stringstream ss;

    ss << std::endl; // blank line
    ss << "OpenGL Driver Info" << std::endl;
    ss << "==================" << std::endl;
    ss << "Vendor: " << this->vendor << std::endl;
    ss << "Version: " << this->version << std::endl;
    ss << "GLSL Version: " << this->glslVersion << std::endl;
    ss << "Renderer: " << this->renderer << std::endl;

    ss << std::endl;
    ss << "Color Bits(R,G,B,A): (" << this->redBits << ", " << this->greenBits
       << ", " << this->blueBits << ", " << this->alphaBits << ")\n";
    ss << "Depth Bits: " << this->depthBits << std::endl;
    ss << "Stencil Bits: " << this->stencilBits << std::endl;

    ss << std::endl;
    ss << "Max Texture Size: " << this->maxTextureSize << "x" << this->maxTextureSize << std::endl;
    ss << "Max Lights: " << this->maxLights << std::endl;
    ss << "Max Clip Planes: " << this->maxClipPlanes << std::endl;
    ss << "Max Modelview Matrix Stacks: " << this->maxModelViewStacks << std::endl;
    ss << "Max Projection Matrix Stacks: " << this->maxProjectionStacks << std::endl;
    ss << "Max Attribute Stacks: " << this->maxAttribStacks << std::endl;
    ss << "Max Texture Stacks: " << this->maxTextureStacks << std::endl;

    ss << std::endl;
    ss << "Total Number of Extensions: " << this->extensions.size() << std::endl;
    ss << "==============================" << std::endl;
    for(unsigned int i = 0; i < this->extensions.size(); ++i)
        ss << this->extensions.at(i) << std::endl;

    ss << "======================================================================" << std::endl;

    std::cout << ss.str() << std::endl;
}

float getTextWidthOnScreen(const std::string& pText, void* pCurrentFont)
{
	int lViewportStats[4];
	glGetIntegerv(GL_VIEWPORT,lViewportStats);
	float lCharSizes = 0.0f;
	for(unsigned int i=0;i<pText.size();i++) {
		lCharSizes += glutBitmapWidth( pCurrentFont,pText[i]) / (float)lViewportStats[2];
	}

	return lCharSizes;
}

void drawText(const std::string& pText,float pXMin,float pYMin,float pXMax,float pYMax,int pFontSize,void* pCurrentFont,bool pAutoLineBreak,bool pFlagCentered)
{
	// We get the viewport size, to be able afterwise to transform pixel
	// sizes in "global viewport" referential.
	int lViewportStats[4];
	glGetIntegerv(GL_VIEWPORT,lViewportStats);
	const float lLineSize = ((float)pFontSize)/lViewportStats[3];

	// If enabled, we execute end-of-line cutted word processing
	std::string lWordCutContent = pText;

	std::vector<float> lVectLineWidths;

	//Word cut
	std::vector<float> lCharSizes;
	{
		for(unsigned int i=0;i<pText.size();i++) {
			lCharSizes.push_back(glutBitmapWidth(pCurrentFont,lWordCutContent[i])/(float)lViewportStats[2]);
	}}
		float lMaxWidth = pXMax-pXMin;
		float lCurrentLineWidth=0;
		float lCurrentWordWidth=0;
		int lStartOfWord=0;
		{for(unsigned int i=0;i<pText.size();i++) {
			if(pText[i] == ' ') {
				lStartOfWord = i;
				lCurrentLineWidth += lCurrentWordWidth + lCharSizes[i];
				lCurrentWordWidth=0;			
			}
			else if(pText[i] == '\n') {
				lStartOfWord = i;
				lVectLineWidths.push_back(lCurrentLineWidth+lCurrentWordWidth);
				lCurrentLineWidth=0;
				lCurrentWordWidth=0;			
			}
			else {
				lCurrentWordWidth += lCharSizes[i];	
				if(pAutoLineBreak) {
					if(lCurrentLineWidth + lCurrentWordWidth > lMaxWidth) {				
						lWordCutContent[lStartOfWord] = '\n';
						lCurrentLineWidth = 0;
					}
				}
			}
		}}
		lVectLineWidths.push_back(lCurrentLineWidth+lCurrentWordWidth);
	

	glRasterPos2f(pXMin,pYMax-lLineSize);	
	
	glPushAttrib(GL_ENABLE_BIT);
	// Text drawing

	glDisable(GL_DEPTH_TEST);

	int lCurrentLine = 1;

	bool aCenteredMode = pFlagCentered;
	if(!aCenteredMode) {
		glRasterPos2f(pXMin,pYMax-lLineSize);
	}
	else {
		glRasterPos2f((pXMax+pXMin)/2-lVectLineWidths[lCurrentLine-1]/2,pYMax-lLineSize);
	}
	float lXPos = pXMin;
	
	for(unsigned int i=0;i < lWordCutContent.size();i++) {
		lXPos += glutBitmapWidth(pCurrentFont,lWordCutContent[i])/(float)lViewportStats[2];
		if(lXPos > pXMax || lWordCutContent[i] == '\n') {
			lCurrentLine++;
			if(!aCenteredMode) {
				glRasterPos2f(pXMin,pYMax-lLineSize*lCurrentLine);
			}
			else {
				glRasterPos2f((pXMax+pXMin)/2-lVectLineWidths[lCurrentLine-1]/2,pYMax-lLineSize*lCurrentLine);
			}
			lXPos = pXMin;
			if(pYMin+lLineSize*lCurrentLine > pYMax) 
				break;			
		}
		else {
			glutBitmapCharacter(pCurrentFont,lWordCutContent[i]);
		}
	}

	glPopAttrib();

}

void drawText3D(const std::string& pText,float pX,float pY,float pZ,void* pCurrentFont)
{
	glRasterPos3f(pX,pY,pZ);	
	
	glPushAttrib(GL_ENABLE_BIT);
	// Text drawing

	glDisable(GL_DEPTH_TEST);

	
	for(unsigned int i=0;i < pText.size();i++) {
	  glutBitmapCharacter(pCurrentFont,pText[i]);
	}

	glPopAttrib();

}

void drawString(const char *str, int x, int y, float color[4], void *font)
{
    glPushAttrib(GL_LIGHTING_BIT | GL_CURRENT_BIT); // lighting and color mask
    glDisable(GL_LIGHTING);     // need to disable lighting for proper text color
    glDisable(GL_TEXTURE_2D);

    glColor4fv(color);          // set text color
    glRasterPos2i(x, y);        // place text position

    // loop all characters in the string
    while(*str)
    {
        glutBitmapCharacter(font, *str);
        ++str;
    }

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_LIGHTING);
    glPopAttrib();
}
